/*
This file is part of La Linea.

La Linea is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

La Linea is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with La Linea.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "FrameCache.h"

using namespace std;

FrameCache::FrameCache() : _mode(VBO) {
}

FrameCache::~FrameCache(){
	for(std::vector<FrameCacheData*>::iterator it = _pool.begin(); it != _pool.end(); ++it) delete (*it);
	_pool.clear();
}

FrameCacheData* FrameCache::buildData(Frame* f){
	assert (f!=NULL);

	vector<OneLineCurve*> olc = f->getLines();
	FrameCacheData* res = new FrameCacheData(f);

	f->setCache(res);

	if(_mode == DISPLAY_LISTS){	// Display lists
		res->_displayListId = f->createDisplayList();
	}
	else if(_mode == VBO) { // VBO
		vector<vector<simple2DVertex>*> points;
		
		res->_vboCount = 0;
		for(vector<OneLineCurve*>::iterator it = olc.begin(); it != olc.end(); ++it){
			vector<simple2DVertex>* v;
			v = (*it)->getVertex();
			if(v != 0){
				points.push_back(v);
				res->_vboCount++;
			}
		}

		if(res->_vboCount > 0){
			res->_vboIds = new GLuint[res->_vboCount];
			res->_vboIndices = new GLsizei[res->_vboCount];

			assert ((res->_vboIds!=NULL) && (res->_vboIndices!=NULL));

			glGenBuffers(res->_vboCount, res->_vboIds);
			for(unsigned int i=0; i<points.size(); i++){
				vector<simple2DVertex>* v = points[i];
				int vCount = v->size();
				res->_vboIndices[i] = vCount;
				glBindBuffer(GL_ARRAY_BUFFER, res->_vboIds[i]);
				glBufferData(GL_ARRAY_BUFFER, vCount*sizeof(simple2DVertex), 0, GL_STATIC_DRAW);
				GLvoid* vboBuffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
				assert (vboBuffer!=NULL);
				memcpy(vboBuffer, &((*v)[0]), vCount*sizeof(simple2DVertex));
				glUnmapBuffer(GL_ARRAY_BUFFER);
			}
		}
		
		for(vector<vector<simple2DVertex>*>::iterator it = points.begin(); it != points.end(); ++it)
			delete (*it);
		points.clear();
	}

	return res;
}

void FrameCache::drawCacheData(FrameCacheData* f){
	if(_mode == DISPLAY_LISTS){
		if(f->_displayListId != -1)
			glCallList(f->_displayListId);
	}
	else if(_mode == VBO){
		if(!f->_vboIds) return;
		for(int i=0; i<f->_vboCount; i++){
			if(glIsBuffer(f->_vboIds[i])){
				glBindBuffer(GL_ARRAY_BUFFER, f->_vboIds[i]);
				glVertexPointer(2, GL_FLOAT, 0,BUFFER_OFFSET(0));	
				glEnableClientState(GL_VERTEX_ARRAY);
				glDrawArrays(GL_TRIANGLE_STRIP, 0, f->_vboIndices[i]);
				glDisableClientState(GL_VERTEX_ARRAY);
			}
		}
	}
}

void FrameCache::invalidate(Frame* f){
	assert (f!=NULL);
	FrameCacheData* old = f->getCache();
	if(old != NULL){
		for(vector<FrameCacheData*>::iterator it = _pool.begin(); it != _pool.end(); ++it){
			if((*it) == old){
				_pool.erase(it);
				break;
			}
		}
		delete old;
	}


	FrameCacheData* newData = buildData(f);
	_pool.push_back(newData);
}

void FrameCache::draw(Frame* f, unsigned int options){
	assert (f!=NULL);
	FrameCacheData* currentData = f->getCache();
	
	// Stencils
	if((options & DRAW_NOSTENCIL) != DRAW_NOSTENCIL) f->drawStencils(options);
	
	// Outline
	if(currentData){
		drawCacheData(currentData);
	}
	else {
		FrameCacheData* newData = buildData(f);
		_pool.push_back(newData);
		drawCacheData(newData);
	}
	
	// Bounding box
	if((options & DRAW_NOCOLOR) != DRAW_NOCOLOR) glColor4f(0.0f, 0.6f, 0.2f, 1.0f);
	if((options & DRAW_BOUNDINGBOX) == DRAW_BOUNDINGBOX){
			f->drawBoundingBox();
	}
}

void FrameCache::flush(){
		for(std::vector<FrameCacheData*>::iterator it = _pool.begin(); it != _pool.end(); ++it){
			delete (*it);
		}
		_pool.clear();
}

bool FrameCache::isInCache(Frame* f){
	assert (f!=NULL);
	for(vector<FrameCacheData*>::iterator it = _pool.begin(); it != _pool.end(); ++it){
		if((*it)->_ref == f){
			return true;
		}
	}
	return false;
}

FrameCacheData* FrameCache::getCacheData(Frame* f){
	assert (f!=NULL);
	FrameCacheData* currentData = f->getCache();

	if(currentData) return currentData;

	FrameCacheData* newData = buildData(f);
	_pool.push_back(newData);

	return newData;
}

void FrameCache::removeEntry(Frame* f){
	assert (f!=NULL);
	for(vector<FrameCacheData*>::iterator it = _pool.begin(); it != _pool.end(); ++it){
		if((*it)->_ref == f){
			delete (*it);
			_pool.erase(it);
			return;
		}
	}
}


void FrameCache::setMode(CacheMode c){ 
	_mode = c;
	flush(); 
}